home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Turnbull China Bikeride
/
Turnbull China Bikeride - Disc 1.iso
/
ARGONET
/
PD
/
GRAPHICS
/
GIF2RPC.SPK
/
source
/
c
/
gif2rpc
< prev
next >
Wrap
Text File
|
1996-01-14
|
64KB
|
2,051 lines
/* gif2rpc.c
* AUTHOR: Cy Booker, cy@cheepnis.demon.co.uk
* LICENSE: FreeWare, Copyright (c) 1995 Cy Booker, but see below
* PURPOSE: convert GIF file to RISC OS sprite
*
* the lzw decoding part contains code that came with the following header:
*
*================================================================================================
* DECODE.C - An LZW decoder for GIF
* Copyright (C) 1987, by Steven A. Bennett
*
* Permission is given by the author to freely redistribute and include
* this code in any program as long as this credit is given where due.
*
* In accordance with the above, I want to credit Steve Wilhite who wrote
* the code which this is heavily inspired by...
*
* GIF and 'Graphics Interchange Format' are trademarks (tm) of
* Compuserve, Incorporated, an H&R Block Company.
*
* Release Notes: This file contains a decoder routine for GIF images
* which is similar, structurally, to the original routine by Steve Wilhite.
* It is, however, somewhat noticably faster in most cases.
*================================================================================================
*
* the GIF decoding is based on the specification:
*
*================================================================================================
* GRAPHICS INTERCHANGE FORMAT(sm)
* Version 89a
* (c)1987,1988,1989,1990
* Copyright
* CompuServe Incorporated
* Columbus, Ohio
*================================================================================================
*
* note that there is no patent infringement involved in DECODING gif files...
*
* everything else you can blame me (cjb) for
*
* oh yeah, usual disclaimers apply...
*
* CJB: 1995.10.01: made processing code a library
*
* TODO: so 16bit output is *quickly* downgraded to 8/4/2/1 if ok to do so
*/
#include <assert.h>
#include <ctype.h>
#include <locale.h> /* so can force lower case */
#include <setjmp.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "OS:colourtrans.h"
#include "OS:hourglass.h"
#include "OS:macros.h"
#include "OS:osfile.h"
#include "OS:osspriteop.h"
#include "gif2rpc:map8bpp.h"
#include "gif2rpc:map16bpp.h"
#include "gif2rpc:process_gif.h"
#include "16bpp_48bit.h.16bpp_48bit"
#include "16bpp_66bit.h.16bpp_66bit"
#include "8bpp.h.8bpp"
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
#ifndef DEBUG_LZW
#define DEBUG_LZW 0
#endif /* DEBUG_LZW */
#define LZW_MAX_CODES (4095)
#define FAIL_NOT_GIF (10)
#define FAIL_CORRUPT_LZW (11)
#define FAIL_SHORT_FILE (12)
#define FAIL_BAD_RECORD (13)
#define FAIL_GRAPHIC_EXTENSION_SIZE (14)
#define FAIL_NO_IMAGE (15)
#define FAIL_IMAGE_DIMENSIONS (16)
#define FAIL_GRAPHIC_EXTENSION_TERMINATOR (17)
#define FAIL_INTERNAL_PROCESS_MASK (18)
#define FAIL_INTERNAL_PROCESS_IMAGE (29)
#define FAIL_NO_PALETTE (21)
#define FAIL_INCOMPLETE_LAST_ROW (22)
#define FAIL_EXTRA_DATA_AFTER_IMAGE (23)
#define FAIL_BAD_EOF_IMAGE (24)
#define GIF2RPC_PARAMS "gif2rpc$Options" /* system variable */
#define SPRITE_NAME "gif2rpc" /* default sprite name */
#define VBUF (1024) /* byte size of log file buffer */
#define YESNO(B) ((B) ? "yes" : "no")
#define MODE(LOG2BPP, XRES, YRES) ((os_mode)(\
(((LOG2BPP) + 1) << osspriteop_TYPE_SHIFT)\
| ((XRES) << osspriteop_YRES_SHIFT)\
| ((YRES) << osspriteop_XRES_SHIFT)\
| 1))
#define LSR(X, S) (((unsigned int)(X)) >> (S)) /* portable */
#define WORD_WIDTH(P) LSR((P) + 31, 5) /* P is BIT width */
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
typedef struct {
byte *bitmap;
int line_length;
} bitmapinfo;
typedef struct {
bitmapinfo source;
bitmapinfo dest;
int bpp;
os_coord pixel_size;
} reducebitmap;
typedef bool (*process_gif_fn)(const process_gif *);
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
static void default_wimppalette(
os_colour *palette,
int lb_bpp);
static byte fail(void);
static process_gif_fn filter_fn(
int index);
static byte get_byte(void);
static byte get_image_byte(void);
static byte get_image_byte_aux(void);
static int get_word(void);
static void gif_get_image_desc(void);
static void gif_lzw_decode(void);
static void gif_parse(void);
static void gif_parse_extension(
int extension);
static byte *gif_row(void);
static void gif_skip_data(void);
static void insert_bytes_before_image(
osspriteop_area *area,
osspriteop_header *sprite,
int siz);
static void load_file(void);
static void log_filename(
const char *name);
static int lzw_get_next_code(void);
static void lzw_init(
int size);
static void prepare_sprite(void);
static void process_cli(
char **argv);
static bool process_gif_32bpp(
const process_gif *p);
static void process_mask(void);
static void process_image(void);
static void reduce_bitmap(
const reducebitmap *rb);
static void reduce_sprite_lb_bpp(
int lb_bpp);
static int stricmp(
const char *sleft,
const char *sright);
static void turn_off_hourglass(void);
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
static struct {
osspriteop_area *area;
osspriteop_header *sprite;
int lb_bpp;
os_coord pixel_size;
os_coord dpi;
bool transparent_p;
bitmapinfo image;
bitmapinfo mask;
int filter; /* index into Gfilters array */
} Gsprite;
static struct GIF {
byte *file_buffer;
unsigned int file_siz;
unsigned int file_pos;
struct {
os_coord size; /* size of screen, in pixels */
int bpp; /* bpp of screen */
int background; /* background palette index (ignored) */
int aspect_ratio; /* weird format */
bool palette_p; /* was a global palette defined? */
bool palette_sorted_p; /* is palette pre-sorted? (ignored) */
int palette_bpp; /* how many bpp available in palette */
os_colour palette[256];
} screen;
struct {
os_coord topleft;
os_coord size; /* technically, should be inside screen */
bool interlace_p;
bool palette_p;
bool palette_sorted_p;
int palette_bpp;
os_colour palette[256]; /* this is the palette for the image */
} image;
struct {
int dispose; /* ignore */
bool user_input_p; /* ignore */
bool transparent_p; /* is the image transparent */
int delay_time; /* ignore */
int transparent; /* this is the transparent colour index */
} control;
bool image_p;
bool control_p; /* control section found? */
int input_bpp; /* ie for image/screen {1,2,3,4,5,6,7,8} */
int output_lb_bpp; /* requested lb_bpp {0,1,2,3,4,5} */
jmp_buf abortion;
} Ggif;
static struct {
int current_size;
int clear;
int ending;
int newcodes;
int top_slot;
int slot;
int nbits_left;
int bad_code_count;
byte b1; /* one byte bit buffer */
byte stack[LZW_MAX_CODES + 1]; /* Stack for storing pixels */
byte suffix[LZW_MAX_CODES + 1]; /* Suffix table */
unsigned int prefix[LZW_MAX_CODES + 1]; /* Prefix linked list */
int row; /* offset in current interlace bit */
int interlace_part; /* 0..3 */
int block_count; /* bytes left in current gif file block */
int real_row; /* used by hourglass off */
} Glzw;
static struct {
char *gif;
char *sprite;
char *filter;
char *log;
char *name;
bool help_p;
bool square_p;
bool fussy_p;
bool quiet_p;
bool hourglass_p;
bool stats_p;
bool accurate_p;
bool force_66_p;
bool autoname_p;
int bpp;
int verbose;
} Gargs;
static process_gif Gpg;
static FILE *Glog = stdout;
static struct {
const char *name;
process_gif_fn fn_8bpp;
process_gif_fn fn_16bpp_48bit;
process_gif_fn fn_16bpp_66bit;
} Gfilters[] = {
#define FILTER(X) {#X, process_gif_8bpp_ ##X, process_gif_16bpp_ ##X## _48bit, process_gif_16bpp_ ##X## _66bit}
/* first */ FILTER(nearest),
/* second */ FILTER(dither2x2),
FILTER(sierra2_4a),
FILTER(floyd_steinberg),
FILTER(sierra3),
FILTER(sierra2),
FILTER(burkes),
FILTER(stucki),
FILTER(jarvis_judice_ninke)};
static struct {
os_t started;
os_t decoded;
os_t processed;
} Gwhen;
static os_colour Goutput_palette[256];
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
int main(
int argc,
char **argv) {
int code;
NOT_USED(argc);
setlocale(LC_ALL, ""); /* current locale, and not C locale */
process_cli(argv);
if (Gargs.stats_p) {
Gwhen.started = os_read_monotonic_time();
}
if (Gargs.log) {
if (!freopen(Gargs.log, "a", stderr)) {
fprintf(stderr, "Failed to redirect output\n");
return EXIT_FAILURE;
}
setvbuf(stderr, NULL, _IONBF, 0);
Glog = stderr;
}
load_file();
code = setjmp(Ggif.abortion);
if (code) {
if (!Gargs.quiet_p) {
fprintf(stderr, "gif2rpc: %s: error %d @ %#x\n", Gargs.gif, code, Ggif.file_pos);
}
return EXIT_FAILURE;
}
gif_parse();
return EXIT_SUCCESS;
}
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
static void add_arg(
char **pcli,
const char *arg) {
char *cli;
assert(pcli);
assert(arg);
cli = (*pcli)
? realloc(*pcli, strlen(*pcli) + 1 + strlen(arg) + 1)
: malloc(strlen(arg) + 1);
if (!cli) {
exit(EXIT_FAILURE);
}
if (*pcli) {
strcat(cli, " ");
strcat(cli, arg);
} else {
strcpy(cli, arg);
}
*pcli = cli;
}
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
static void gif_parse(void) {
bits flags;
int i, size;
os_colour colour;
byte record;
byte dummy;
int code;
int bpp, siz, in_file_siz, out_file_siz, spr_file_siz;
bits duration_decode, duration_process, duration_all;
os_error *error;
if ((get_byte() != 'G')
|| (get_byte() != 'I')
|| (get_byte() != 'F')) {
longjmp(Ggif.abortion, FAIL_NOT_GIF);
}
dummy = get_byte();
dummy = get_byte();
dummy = get_byte(); /* skip version string */
/*
* read screen description
*/
Ggif.screen.size.x = get_word();
Ggif.screen.size.y = get_word();
flags = get_byte();
Ggif.screen.bpp = LSR(flags & 0x70, 4) + 1;
Ggif.screen.palette_bpp = (flags & 0x07) + 1;
Ggif.screen.palette_p = ((flags & 0x80) != NONE);
Ggif.screen.palette_sorted_p = ((flags & 0x08) != NONE);
Ggif.screen.background = get_byte();
Ggif.screen.aspect_ratio = get_byte();
if (Ggif.screen.palette_p) {
/*
* global colour map
*/
size = 1 << Ggif.screen.palette_bpp;
for (i= 0; (i < size); i++) {
colour = get_byte() << 8; /* should we do get_byte() * 255 / ((1 << palette_bpp) - 1) ? */
colour |= get_byte() << 16;
colour |= get_byte() << 24;
Ggif.screen.palette[i] = colour;
}
}
if (Gargs.verbose > 0) {
fprintf(Glog, " screen size= (%d, %d), screen bpp= %d, palette_bpp= %d, background= %d, palette= %s, sorted= %s, aspect_ratio= %d\n",
Ggif.screen.size.x, Ggif.screen.size.y,
Ggif.screen.bpp,
Ggif.screen.palette_bpp,
Ggif.screen.background,
YESNO(Ggif.screen.palette_p),
YESNO(Ggif.screen.palette_sorted_p),
Ggif.screen.aspect_ratio);
}
for (;;) {
record = get_byte();
switch (record) {
case 0x2c:
if (Ggif.image_p) {
/* ignore multiple images */
return;
}
gif_get_image_desc();
if ((Ggif.image.palette_p == NONE) && (Ggif.screen.palette_p == NONE)) {
longjmp(Ggif.abortion, FAIL_NO_PALETTE);
}
Ggif.image_p = TRUE;
Ggif.input_bpp = (Ggif.image.palette_p) ? Ggif.image.palette_bpp : Ggif.screen.palette_bpp;
prepare_sprite();
if (Gargs.hourglass_p) {
atexit(turn_off_hourglass);
xhourglass_on();
}
if (!Gargs.fussy_p) {
/*
* set up a dummy jump buffer to catch any and all errors in the decoding
*/
code = setjmp(Ggif.abortion);
if (code == 0) {
gif_lzw_decode();
} else {
if (!Gargs.quiet_p) {
fprintf(stderr, "gif2rpc: %s: (ignored) error %d @ %#x\n", Gargs.gif, code, Ggif.file_pos);
}
}
/*
* repeat code in main to handle any processing bugs!
*/
code = setjmp(Ggif.abortion);
if (code) {
if (!Gargs.quiet_p) {
fprintf(stderr, "gif2rpc: %s: error %d @ %#x\n", Gargs.gif, code, Ggif.file_pos);
}
exit(EXIT_FAILURE);
}
} else {
gif_lzw_decode();
}
if (Gargs.stats_p) {
Gwhen.decoded = os_read_monotonic_time();
}
if (Gargs.hourglass_p) {
xhourglass_percentage(0);
}
if (Gsprite.transparent_p) {
/*
* must generate a mask from raw 8-bit data before processing `image'
*
*/
process_mask();
}
process_image();
if (Gargs.verbose > 0) {
fprintf(Glog, " saving `%s', size %#x\n", Gargs.sprite, Gsprite.area->used);
}
if (Gargs.hourglass_p) {
xhourglass_percentage(0);
}
if (Gargs.stats_p) {
Gwhen.processed = os_read_monotonic_time();
}
/*
* save sprite file
*/
error = xosspriteop_save_sprite_file(osspriteop_USER_AREA, Gsprite.area, Gargs.sprite);
if (error) {
if (!Gargs.quiet_p) {
fprintf(stderr, "gif2rpc: error %#x `%s' saving file `%s'\n",
error->errnum, error->errmess, Gargs.sprite);
}
}
if (Gargs.stats_p) {
duration_decode = Gwhen.decoded - Gwhen.started;
duration_process = Gwhen.processed - Gwhen.decoded;
duration_all = os_read_monotonic_time() - Gwhen.started;
fprintf(Glog, "gif2rpc ");
log_filename(Gargs.gif);
fprintf(Glog, "(%4dx%4d:%d%c%c.%c%c%c.%c) -> ",
Gsprite.pixel_size.x,
Gsprite.pixel_size.y,
Ggif.input_bpp,
(Ggif.screen.palette_p) ? 'p' : ' ',
(Ggif.screen.palette_sorted_p) ? 's' : ' ',
(Ggif.image.interlace_p) ? 'i' : ' ',
(Ggif.image.palette_p) ? 'p' : ' ',
(Ggif.image.palette_sorted_p) ? 's' : ' ',
((Ggif.control_p) && (Ggif.control.transparent_p)) ? 't' : ' ');
log_filename(Gargs.sprite);
in_file_siz = Ggif.file_siz;
out_file_siz = Gsprite.area->used - 4;
bpp = 1 << ("01223333"[Ggif.input_bpp-1] - '0');
siz = Gsprite.pixel_size.y * 4 * WORD_WIDTH(Gsprite.pixel_size.x * bpp);
spr_file_siz = (sizeof(osspriteop_area) - 4)
+ sizeof(osspriteop_header)
+ (256 * sizeof(os_colour_pair))
+ siz
+ ((Gsprite.transparent_p) ? siz : 0);
fprintf(Glog, "(%.3s) %8d %6.2f %6.2f%% %s\n",
" 2 4 1625632K16M"+(3*Gsprite.lb_bpp),
spr_file_siz - out_file_siz,
duration_all / 100.0,
(duration_process * 100.0) / (double)duration_all,
Gfilters[Gsprite.filter].name);
}
if (!Gargs.fussy_p) {
/*
* don't bother checking for multiple images, or validating an end of file
*/
return;
}
break;
case 0x21:
gif_parse_extension(get_byte());
break;
case 0x3b: /* marks end of file */
if (!Ggif.image_p) {
longjmp(Ggif.abortion, FAIL_NO_IMAGE);
}
return;
default:
if (!Gargs.quiet_p) {
fprintf(stderr, "gif2rpc: %s: Bad record %#x\n", Gargs.gif, record);
}
longjmp(Ggif.abortion, FAIL_BAD_RECORD);
}/* switch (record) */
}/* for (;;) */
}
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
static void gif_parse_extension(
int extension) {
int size, i;
byte buffer[256];
switch (extension) {
case 0x00: /* change sierra2_4a behaviour! */
break;
case 0xf9: /* graphic control */
if (Gargs.verbose > 1) {
fprintf(Glog, " processing graphic control extension @ %#x\n", Ggif.file_pos);
}
size = get_byte();
if (size != 4) {
longjmp(Ggif.abortion, FAIL_GRAPHIC_EXTENSION_SIZE);
}
for (i= 0; (i < size); i++) {
buffer[i] = get_byte();
}
Ggif.control_p = TRUE;
Ggif.control.dispose = LSR(buffer[0] & 0x1c, 2);
Ggif.control.user_input_p = ((buffer[0] & 0x02) != NONE);
Ggif.control.transparent_p = ((buffer[0] & 0x01) != NONE);
Ggif.control.delay_time = buffer[1] + (256 * buffer[2]);
if (Ggif.control.transparent_p) {
Ggif.control.transparent = buffer[3];
}
if (get_byte() != 0) {
longjmp(Ggif.abortion, FAIL_GRAPHIC_EXTENSION_TERMINATOR);
}
break;
case 0xfe: /* comment, in 7-bit ascii */
if (Gargs.verbose > 1) {
fprintf(Glog, " skipping comment extension @ %#x\n", Ggif.file_pos);
}
gif_skip_data();
break;
case 0x01: /* plain text [a built in text `image'] */
if (Gargs.verbose > 1) {
fprintf(Glog, " skipping text extension @ %#x\n", Ggif.file_pos);
}
gif_skip_data();
break;
case 0xff: /* application */
if (Gargs.verbose > 1) {
fprintf(Glog, " skipping application extension @ %#x\n", Ggif.file_pos);
}
gif_skip_data();
break;
default:
if (Gargs.verbose > 1) {
fprintf(Glog, " skipping unknown extension %d @ %#x\n", extension, Ggif.file_pos);
}
gif_skip_data();
}
}
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
static void gif_get_image_desc(void) {
bits flags;
int i, size;
os_colour colour;
if (Gargs.verbose > 1) {
fprintf(Glog, " reading image description @ %#x\n", Ggif.file_pos);
}
Ggif.image.topleft.x = get_word();
Ggif.image.topleft.y = get_word();
Ggif.image.size.x = get_word();
Ggif.image.size.y = get_word();
if ((Ggif.image.size.x < 1) || (Ggif.image.size.y < 1)) {
longjmp(Ggif.abortion, FAIL_IMAGE_DIMENSIONS);
}
flags = get_byte();
Ggif.image.palette_bpp = (flags & 0x07) + 1;
Ggif.image.palette_sorted_p = ((flags & 0x20) != NONE);
Ggif.image.interlace_p = ((flags & 0x40) != NONE);
Ggif.image.palette_p = ((flags & 0x80) != NONE);
if (Gargs.verbose > 0) {
fprintf(Glog, " image @ (%d, %d), size (%d, %d), bpp= %d, interlaced= %s, palette= %s, sorted= %s\n",
Ggif.image.topleft.x, Ggif.image.topleft.y, Ggif.image.size.x, Ggif.image.size.y,
Ggif.image.palette_bpp, YESNO(Ggif.image.interlace_p), YESNO(Ggif.image.palette_p), YESNO(Ggif.image.palette_sorted_p));
}
i = 0;
if (Ggif.image.palette_p) {
/*
* global colour map
*/
size = 1 << Ggif.image.palette_bpp;
for (; (i < size); i++) {
colour = get_byte() << 8;
colour |= get_byte() << 16;
colour |= get_byte() << 24;
Ggif.image.palette[i] = colour;
}
}
for (size = 1 << Ggif.screen.palette_bpp; (i < size); i++) {
Ggif.image.palette[i] = Ggif.screen.palette[i];
}
/*
* we now force the `transparent' entry to black so that does not dither!
*/
if ((Ggif.control_p) && (Ggif.control.transparent_p)) {
Ggif.image.palette[Ggif.control.transparent] = os_COLOUR_BLACK;
}
}
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
* here the GIF file is expecting the initial minimum code size byte
* what we do is expand the stream into a 256 colour image
* where each row is Gsprite.pixel_size.x bytes across, starting at Gsprite.image.bitmap
* and each row is Gsprite.image.line_length bytes apart
*/
static void gif_lzw_decode(void) {
register byte *sp;
register byte *bufptr;
const byte *bufend;
int code, fc, oc;
int c, size, guard;
/* Initialize for decoding a new image...
*/
size = get_byte();
if ((size < 2) || (size > 9)) {
longjmp(Ggif.abortion, FAIL_CORRUPT_LZW);
}
lzw_init(size);
/* Initialize in case they forgot to put in a clear code.
* (This shouldn't happen, but we'll try and decode it anyway...)
*/
oc = fc = 0;
/* Set up the Glzw.stack pointer and decode buffer pointer
*/
sp = Glzw.stack;
bufptr = gif_row(); bufend = bufptr + Gsprite.pixel_size.x;
/* This is the main loop. For each code we get we pass through the
* linked list of Glzw.prefix codes, pushing the corresponding "character" for
* each code onto the Glzw.stack. When the list reaches a single "character"
* we push that on the Glzw.stack too, and then start unGlzw.stacking each
* character for output in the correct order. Special handling is
* included for the Glzw.clear code, and the whole thing ends when we get
* an Glzw.ending code.
*/
while (c = lzw_get_next_code(), (c != Glzw.ending)) {
#if DEBUG_LZW
fprintf(stderr, "LZW(%#x)\n", c);
#endif /* DEBUG_LZW */
if (c == Glzw.clear) {
#if DEBUG_LZW
fprintf(stderr, "LZW_CLEAR(%#x)\n", c);
#endif /* DEBUG_LZW */
/*
* If the code is a Glzw.clear code, reinitialize all necessary items.
*/
Glzw.current_size = size + 1;
Glzw.slot = Glzw.newcodes;
Glzw.top_slot = 1 << Glzw.current_size;
#ifndef NDEBUG
/*
* help spot bugs/errors quicker
*/
memset(Glzw.prefix, 0xee, sizeof(Glzw.prefix));
#endif /* NDEBUG */
/* Continue reading codes until we get a non-Glzw.clear code
* (Another unlikely, but possible case...)
*/
while ((c = lzw_get_next_code()) == Glzw.clear) {
/* do nothing */
}
#if DEBUG_LZW
fprintf(stderr, "LZW_POST_CLEAR(%#x)\n", c);
#endif /* DEBUG_LZW */
/* If we get an Glzw.ending code immediately after a Glzw.clear code
* (Yet another unlikely case), then break out of the loop.
*/
if (c == Glzw.ending) {
break;
}
/* Finally, if the code is beyond the range of already set codes,
* (This one had better NOT happen... I have no idea what will
* result from this, but I doubt it will look good...) then set it
* to color zero.
*/
if (c >= Glzw.slot) {
c = 0;
}
if (c >= 0x100) {
/*
* a corrupt file
*/
if (!Gargs.quiet_p) {
fprintf(stderr, "gif2rpc: %s: lzw-code %d too large\n", Gargs.gif, c);
}
if (Gargs.fussy_p) {
longjmp(Ggif.abortion, FAIL_CORRUPT_LZW);
}
break;
}
assert(c >= 0);
assert(c < 0x100);
oc = fc = c;
/* And let us not forget to put the char into the buffer... And
* if, on the off chance, we were exactly one pixel from the end
* of the line, we have to send the buffer to the out_line()
* routine...
*/
*bufptr++ = c;
if (bufptr == bufend) {
bufptr = gif_row(); bufend = bufptr + Gsprite.pixel_size.x;
}
} else {
/* In this case, it's not a clear code or an ending code, so
* it must be a code code... So we can now decode the code into
* a stack of character codes. (clear as mud, right?)
*/
code = c;
/* Here we go again with one of those off chances... If, on the
* off chance, the code we got is beyond the range of those already
* set up (Another thing which had better NOT happen...) we trick
* the decoder into thinking it actually got the last code read.
* (Hmmn... I'm not sure why this works... But it does...)
*/
if (code >= Glzw.slot) {
if (code > Glzw.slot) {
Glzw.bad_code_count++;
}
code = oc;
*sp++ = fc;
#if DEBUG_LZW
fprintf(stderr, "LZW_byteOC(%#x)\n", sp[-1]);
#endif /* DEBUG_LZW */
}
/* Here we scan back along the linked list of prefixes, pushing
* helpless characters (ie. suffixes) onto the stack as we do so.
*/
for (guard= LZW_MAX_CODES; ((code >= Glzw.newcodes) && (guard > 0)); guard--) {
assert(code >= 0);
assert(code < COUNT(Glzw.suffix));
*sp++ = Glzw.suffix[code];
#if DEBUG_LZW
fprintf(stderr, "LZW_byte(%#x)\n", sp[-1]);
#endif /* DEBUG_LZW */
code = Glzw.prefix[code];
}
if (code >= 0x100) {
/*
* a corrupt file
*/
if (!Gargs.quiet_p) {
fprintf(stderr, "gif2rpc: %s: lzw-code %d too large\n", Gargs.gif, code);
}
if (Gargs.fussy_p) {
longjmp(Ggif.abortion, FAIL_CORRUPT_LZW);
}
break;
}
assert(code >= 0);
assert(code < 0x100);
/* Push the last character on the Glzw.stack, and set up the new
* Glzw.prefix and Glzw.suffix, and if the required Glzw.slot number is greater
* than that allowed by the current bit size, increase the bit
* size. (NOTE - If we are all full, we *don't* save the new
* Glzw.suffix and Glzw.prefix... I'm not certain if this is correct...
* it might be more proper to overwrite the last code...
*/
*sp++ = code;
#if DEBUG_LZW
fprintf(stderr, "LZW_byteCODE(%#x)\n", sp[-1]);
#endif /* DEBUG_LZW */
if (Glzw.slot < Glzw.top_slot) {
assert(Glzw.slot >= 0);
assert(Glzw.slot < 0x100);
Glzw.suffix[Glzw.slot] = fc = code;
Glzw.prefix[Glzw.slot++] = oc;
oc = c;
}
if (Glzw.slot >= Glzw.top_slot) {
if (Glzw.current_size < 12) {
Glzw.top_slot <<= 1;
Glzw.current_size++;
}
}
/* Now that we've pushed the decoded string (in reverse order)
* onto the Glzw.stack, lets pop it off and put it into our decode
* buffer... And when the decode buffer is full, write another
* line...
*/
while (sp > Glzw.stack) {
*bufptr++ = *(--sp);
if (bufptr == bufend) {
bufptr = gif_row(); bufend = bufptr + Gsprite.pixel_size.x;
}
}
}
}
if ((bufptr != bufend) && ((bufend-bufptr) != Gsprite.pixel_size.x)) {
/*
* this probably indicates an error
* but do NOT fill it because some files seem to have an ``extra'' row!
*/
if (!Gargs.quiet_p) {
fprintf(stderr, "gif2rpc: %s: missing %d pixels off last row\n", Gargs.gif, bufend - bufptr);
}
if (Gargs.fussy_p) {
longjmp(Ggif.abortion, FAIL_INCOMPLETE_LAST_ROW);
}
}
if (Glzw.block_count != 0) {
/*
* once again, this PROBABLY indicates an error
* but have seen sprites where an extra `0' byte exists
*/
if (!Gargs.quiet_p) {
fprintf(stderr, "gif2rpc: %s: extra %d byte(s) at end of image\n", Gargs.gif, Glzw.block_count);
}
if (Gargs.fussy_p) {
longjmp(Ggif.abortion, FAIL_EXTRA_DATA_AFTER_IMAGE);
}
} else {
for (guard= Glzw.block_count; (guard); guard--) {
code = get_byte();
}
}
if (get_byte() != 0) {
if (Gargs.fussy_p) {
if (!Gargs.quiet_p) {
fprintf(stderr, "gif2rpc: %s: unexpected byte at end of image\n", Gargs.gif);
}
longjmp(Ggif.abortion, FAIL_BAD_EOF_IMAGE);
}
}
}
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
static void lzw_init(
int size) {
Glzw.current_size = size + 1;
Glzw.top_slot = 1 << Glzw.current_size;
Glzw.clear = 1 << size;
Glzw.ending = Glzw.clear + 1;
Glzw.slot = Glzw.newcodes = Glzw.ending + 1;
Glzw.nbits_left = 0;
Glzw.row = 0;
Glzw.interlace_part = 0;
Glzw.block_count = 0;
Glzw.real_row = 0;
}
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
static int lzw_get_next_code(void) {
unsigned int ret;
if (Glzw.nbits_left == 0) {
Glzw.b1 = get_image_byte();
Glzw.nbits_left = 8;
}
ret = LSR(Glzw.b1, (8 - Glzw.nbits_left));
while (Glzw.current_size > Glzw.nbits_left) {
Glzw.b1 = get_image_byte();
ret |= Glzw.b1 << Glzw.nbits_left;
Glzw.nbits_left += 8;
}
Glzw.nbits_left -= Glzw.current_size;
ret &= ~((~0) << Glzw.current_size);
return ret;
}
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
static byte *gif_row(void) {
int row;
static const int Ggif_interlace_offset[4] = {0, 4, 2, 1}; /* The way Interlaced image should. */
static const int Ggif_interlace_jumps[4] = {8, 8, 4, 2}; /* be read - offsets and jumps... */
#if DEBUG_LZW
fprintf(stderr, "gif_row()\n");
#endif /* DEBUG_LZW */
if (Gargs.hourglass_p) {
row = Glzw.real_row++;
xhourglass_percentage((row * 100) / Gsprite.pixel_size.y);
}
row = Glzw.row++;
if (Ggif.image.interlace_p) {
assert(Glzw.interlace_part < 4);
row *= Ggif_interlace_jumps[Glzw.interlace_part];
row += Ggif_interlace_offset[Glzw.interlace_part];
if (row >= Ggif.image.size.y) {
Glzw.interlace_part++;
if (Glzw.interlace_part < 4) {
row = Ggif_interlace_offset[Glzw.interlace_part];
Glzw.row = 1;
}
}
}
if (Gargs.verbose > 1) {
fprintf(Glog, " processing row %d @ %#x in file\n", row, Ggif.file_pos);
}
row = MIN(row, Ggif.image.size.y - 1);
return &Gsprite.image.bitmap[row * Gsprite.image.line_length];
}
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
static void gif_skip_data(void) {
int size;
byte dummy;
for (; (size = get_byte(), (size > 0));) {
do {
dummy = get_byte();
size--;
} while (size != 0);
}
}
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
static byte get_image_byte(void) {
if (Glzw.block_count == 0) {
return get_image_byte_aux();
}
Glzw.block_count--;
return get_byte();
}
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
static byte get_image_byte_aux(void) {
Glzw.block_count = get_byte();
return get_image_byte();
}
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
static byte get_byte(void) {
if (Ggif.file_pos >= Ggif.file_siz) {
return fail();
}
return Ggif.file_buffer[Ggif.file_pos++];
}
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
static int get_word(void) {
unsigned int i;
i = get_byte();
return i | (get_byte() << 8);
}
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
static byte fail(void) {
longjmp(Ggif.abortion, FAIL_SHORT_FILE);
return '\0';
}
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
* this should be called AFTER know gif image size and palette
* it sets up the Gsprite variable
*/
static void prepare_sprite(void) {
int aspect_ratio;
bits flags;
int lb_bpp;
os_mode mode;
size_t area_size;
char *name;
int i;
osspriteop_header *sprite;
char name_buffer[osspriteop_NAME_LIMIT + 1];
os_error *error;
int palette_size;
Gsprite.pixel_size = Ggif.image.size; /* structure copy */
assert(Gsprite.pixel_size.x > 0);
assert(Gsprite.pixel_size.y > 0);
aspect_ratio = 15 + ((Ggif.screen.aspect_ratio) ? Ggif.screen.aspect_ratio : 49);
if (aspect_ratio != 64) {
if (Gargs.verbose > 0) {
fprintf(Glog, " aspect ratio is %d/64\n", aspect_ratio);
}
if (Gargs.square_p) {
aspect_ratio = 64;
}
}
Gsprite.dpi.x = (90 * aspect_ratio) / 64;
Gsprite.dpi.y = 90;
Gsprite.transparent_p = ((Ggif.control_p) ? (Ggif.control.transparent_p) : FALSE);
palette_size = 0;
switch (Gargs.bpp) {
case -1:
flags = os_read_mode_variable(os_CURRENT_MODE, os_MODEVAR_LOG2_BPP, &lb_bpp);
if ((flags & _C) != NONE) {
if (!Gargs.quiet_p) {
fprintf(stderr, "gif2rpc: failed to find screen mode\n");
}
exit(EXIT_FAILURE);
}
if (lb_bpp <= 3) {
error = xcolourtrans_read_palette(
(osspriteop_area const *)colourtrans_CURRENT_MODE,
(osspriteop_id)colourtrans_CURRENT_PALETTE,
(os_palette *)Goutput_palette,
sizeof(Goutput_palette),
NONE,
NULL);
if (error) {
if (!Gargs.quiet_p) {
fprintf(stderr, "gif2rpc: unable to read palette (%#x): '%s'\n", error->errnum, error->errmess);
}
exit(EXIT_FAILURE);
}
palette_size = sizeof(os_colour_pair) * (1 << (1 << lb_bpp));
}
break;
case 0:
lb_bpp = 3;
palette_size = sizeof(os_colour_pair) * 256; /* always 256 entries now */
if (Ggif.input_bpp < 1) {
assert(!"internal error");
} else if (Ggif.input_bpp < 2) {
lb_bpp = 0;
} else if (Ggif.input_bpp < 3) {
lb_bpp = 1;
} else if (Ggif.input_bpp < 5) {
lb_bpp = 2;
}
break;
case 1: lb_bpp = 0; break;
case 2: lb_bpp = 1; break;
case 4: lb_bpp = 2; break;
case 8: lb_bpp = 3; break;
case 16: lb_bpp = 4; break;
case 32: lb_bpp = 5; break;
default:
if (!Gargs.quiet_p) {
fprintf(stderr, "gif2rpc: invalid command line: bpp value %d, should be one of {0,1,2,4,8,16,32}\n", Gargs.bpp);
}
exit(EXIT_FAILURE);
}
if ((lb_bpp <= 3) && (palette_size == 0)) {
default_wimppalette(Goutput_palette, lb_bpp);
}
Ggif.output_lb_bpp = lb_bpp;
/*
* we will generate a 256 colour sprite, and then downgrade it if necessary
*/
lb_bpp = MAX(3, lb_bpp);
Gsprite.lb_bpp = lb_bpp;
if (Gargs.name == NULL) {
if (Gargs.autoname_p) {
int len;
name = strrchr(Gargs.gif, '.');
if (!name) {
name = strrchr(Gargs.gif, ':');
}
if (!name) {
name = Gargs.gif;
} else {
name++;
}
len = strlen(name);
if (len > osspriteop_NAME_LIMIT) {
name += len - osspriteop_NAME_LIMIT;
}
name = strcpy(name_buffer, name);
} else {
if (sizeof(SPRITE_NAME) >= sizeof(name_buffer)) {
abort();
}
name = strcpy(name_buffer, SPRITE_NAME);
}
} else {
name = Gargs.name;
}
for (i= 0; (i < osspriteop_NAME_LIMIT); i++) {
if ((name[i] <= 32) || (name[i] == 0x7f)) {
break;
}
name[i] = (char)tolower(name[i]);
}
name[i] = '\0';
mode = (lb_bpp == 3) ? os_MODE8BPP90X90 : MODE(lb_bpp, Gsprite.dpi.x, Gsprite.dpi.y);
Gsprite.image.line_length = 4 * WORD_WIDTH(Gsprite.pixel_size.x * (1 << lb_bpp));
Gsprite.mask.line_length = 4 * WORD_WIDTH(Gsprite.pixel_size.x * ((lb_bpp <= 3) ? (1 << lb_bpp) : 1));
/*
* get a sprite area big enough
*/
area_size = sizeof(osspriteop_area)
+ sizeof (osspriteop_header)
+ palette_size
+ (Gsprite.image.line_length * Gsprite.pixel_size.y)
+ ((Gsprite.transparent_p)
? (Gsprite.mask.line_length * Gsprite.pixel_size.y)
: 0);
Gsprite.area = malloc(area_size);
if (!Gsprite.area) {
if (!Gargs.quiet_p) {
fprintf(stderr, "gif2rpc: out of memory (need %u)\n", area_size);
}
exit(EXIT_FAILURE);
}
Gsprite.area->size = area_size;
Gsprite.area->first = sizeof(osspriteop_area);
osspriteop_clear_sprites(osspriteop_USER_AREA, Gsprite.area);
osspriteop_create_sprite(osspriteop_NAME, Gsprite.area, name, FALSE, Gsprite.pixel_size.x, Gsprite.pixel_size.y, mode);
sprite = osspriteop_select_sprite(osspriteop_NAME, Gsprite.area, (osspriteop_id)name);
assert(sprite);
Gsprite.sprite = sprite;
if (Gsprite.transparent_p) {
osspriteop_create_mask(osspriteop_PTR, Gsprite.area, (osspriteop_id)sprite);
}
if (palette_size != 0) {
int i;
os_colour_pair *palette = (os_colour_pair *)(sprite+1);
const os_colour *in;
insert_bytes_before_image(Gsprite.area, sprite, palette_size);
/*
* manually insert palette
*/
in = (Gargs.bpp == 0) ? Ggif.image.palette : Goutput_palette;
for (i= 0; (i < (palette_size / sizeof(os_colour_pair))); i++) {
palette[i].on = palette[i].off = in[i];
}
}
Gsprite.image.bitmap = ((byte *)sprite) + sprite->image;
Gsprite.mask.bitmap = ((byte *)sprite) + sprite->mask;
}
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
static void insert_bytes_before_image(
osspriteop_area *area,
osspriteop_header *sprite,
int siz) {
assert(area);
assert(sprite);
assert(siz >= 0);
area->used += siz;
assert(area->used <= area->size);
sprite->size += siz;
sprite->image += siz;
sprite->mask += siz;
}
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
static void load_file(void) {
int type;
bits file_type;
int siz;
if (Gargs.verbose > 0) {
fprintf(Glog, "processing GIF file `%s'\n", Gargs.gif);
}
type = osfile_read_stamped(Gargs.gif, NULL, NULL, (int *)&Ggif.file_siz, NULL, &file_type);
if (type != osfile_IS_FILE) {
if (!Gargs.quiet_p) {
fprintf(stderr, "gif2rpc: %s: this is not a (GIF) file\n", Gargs.gif);
}
exit(EXIT_FAILURE);
}
if (Gargs.fussy_p) {
if (file_type != 0xff0) {
if (!Gargs.quiet_p) {
fprintf(stderr, "gif2rpc: %s: this is not typed as a GIF file\n", Gargs.gif);
}
exit(EXIT_FAILURE);
}
}
if (Gargs.verbose > 1) {
fprintf(Glog, " GIF file is %#x bytes long\n", Ggif.file_siz);
}
Ggif.file_buffer = malloc(MAX(1, Ggif.file_siz));
if (!Ggif.file_buffer) {
if (!Gargs.quiet_p) {
fprintf(stderr, "gif2rpc: out of memory (need %u)\n", Ggif.file_siz);
}
exit(EXIT_FAILURE);
}
osfile_load_stamped(Gargs.gif, (byte *)Ggif.file_buffer, NULL, NULL, &siz, NULL);
if (siz != Ggif.file_siz) {
if (!Gargs.quiet_p) {
fprintf(stderr, "gif2rpc: %s: cant read file\n", Gargs.gif);
}
exit(EXIT_FAILURE);
}
}
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
static void turn_off_hourglass(void) {
xhourglass_off();
}
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
static void process_mask(void) {
const byte *source;
byte *dest;
int x, y;
byte transparent;
int width;
unsigned int b;
source = Gsprite.image.bitmap;
dest = Gsprite.mask.bitmap;
assert(dest);
transparent = Ggif.control.transparent;
width = Gsprite.pixel_size.x;
switch (Gsprite.lb_bpp) {
case 3:
/*
* create an 8bpp mask
*/
if (Gargs.verbose > 0) {
fprintf(Glog, " created 8bpp transparency mask\n");
}
for (y= Gsprite.pixel_size.y; (y > 0); y--) {
for (x= width-1; (x >= 0); x--) {
dest[x] = (source[x] == transparent) ? 0x00 : 0xff;
}
source += Gsprite.image.line_length;
dest += Gsprite.mask.line_length;
}
break;
case 4:
case 5:
/*
* create a 1bpp mask
*/
if (Gargs.verbose > 0) {
fprintf(Glog, " created 1bpp transparency mask\n");
}
for (y= Gsprite.pixel_size.y; (y > 0); y--) {
b = ~0u;
for (x= 0; (x < width);) {
if (source[x] == transparent) {
b &= ~(1u << (x & 0x1f));
}
x++;
if ((x & 0x1f) == 0) {
*(((unsigned int *)dest) + LSR(x, 5) - 1) = b;
b = ~0u;
}
}
if (x & 0x1f) {
*(((unsigned int *)dest) + LSR(x, 5)) = b;
}
source += Gsprite.image.line_length;
dest += Gsprite.mask.line_length;
}
break;
default:
assert(!"internal error");
longjmp(Ggif.abortion, FAIL_INTERNAL_PROCESS_MASK);
}
}
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
static void process_image(void) {
int i;
process_gif_fn fn;
Gpg.buffer = Gsprite.image.bitmap;
Gpg.pixel_width = Gsprite.pixel_size.x;
Gpg.pixel_height = Gsprite.pixel_size.y;
Gpg.line_length = Gsprite.image.line_length;
Gpg.in_palette.colours = Ggif.image.palette;
Gpg.in_palette.ncolours = 1 << Ggif.input_bpp;
switch (Gsprite.lb_bpp) {
case 3:
/*
* this is a special case
*/
if (Gargs.bpp == 0) {
/*
* we want to preserve as much info as possible of the GIF
* but if can reduce bpp then might as well:
*/
if (Ggif.output_lb_bpp < 3) {
reduce_sprite_lb_bpp(Ggif.output_lb_bpp);
}
return;
}
Gpg.fn = map_scaled_rgb_to_palette_index;
if ((!Gargs.accurate_p) && (Ggif.output_lb_bpp == 3) && (Ggif.input_bpp != 0)) {
Gpg.fn = map_scaled_rgb_to_8bpp_colour_number_quick;
}
Gpg.out_palette.ncolours = 1 << (1 << Ggif.output_lb_bpp);
Gpg.out_palette.colours = malloc(sizeof(*Gpg.out_palette.colours) * Gpg.out_palette.ncolours);
if (!Gpg.out_palette.colours) {
if (!Gargs.quiet_p) {
fprintf(stderr, "gif2rpc: %s: out of memory\n", Gargs.gif);
}
return;
}
{
int index;
const os_colour *palette;
rgbtuple *tuples;
os_colour colour;
int red, grn, blu;
palette = Goutput_palette;
tuples = Gpg.out_palette.colours;
for (index= Gpg.out_palette.ncolours - 1; (index >= 0); index--) {
colour = palette[index];
red = (colour >> (1 * 8)) & 0xff; red |= red << 8;
grn = (colour >> (2 * 8)) & 0xff; grn |= grn << 8;
blu = (colour >> (3 * 8)) & 0xff; blu |= blu << 8;
tuples[index].blu = blu;
tuples[index].grn = grn;
tuples[index].red = red;
}
}
break;
case 4:
Gpg.fn = (Gargs.accurate_p)
? map_scaled_rgb_to_16bpp_colour_accurate
: map_scaled_rgb_to_16bpp_colour_quick;
break;
case 5:
Gpg.fn = NULL;
break;
default:
assert(!"internal error");
}
if (!Gargs.filter) {
i = 1; /* 2x2 dither */
} else {
for (i= 0; (i < COUNT(Gfilters)); i++) {
if (stricmp(Gfilters[i].name, Gargs.filter) == 0) {
break;
}
}
if (i == COUNT(Gfilters)) {
if (!Gargs.quiet_p) {
fprintf(stderr, "gif2rpc: unrecognised filter `%s' specified on command line\n",
Gargs.filter);
}
if (Gargs.fussy_p) {
exit(EXIT_FAILURE);
}
i = 1; /* 2x2 dither */
}
}
assert(i >= 0);
assert(i < COUNT(Gfilters));
Gsprite.filter = i;
if (Gargs.verbose > 0) {
fprintf(Glog,
" %s conversion to %dbpp\n",
Gfilters[Gsprite.filter].name, 1 << Gsprite.lb_bpp);
}
fn = filter_fn(Gsprite.filter);
if (!fn) {
if (!Gargs.quiet_p) {
fprintf(stderr,
"gif2rpc: %s: %s conversion to %dbpp not implemented, using 2x2 dither\n",
Gargs.gif, Gfilters[i].name, 1 << Gsprite.lb_bpp);
}
/*
* default to 2x2 dither if not implemented others yet
*/
Gsprite.filter = 1;
fn = filter_fn(Gsprite.filter);
assert(fn);
}
if ((*fn)(&Gpg)) {
/*
* oh dear, out of memory
*/
if (!Gargs.quiet_p) {
fprintf(stderr,
"gif2rpc: %s: out of memory using %s filter\n",
Gargs.gif, Gfilters[Gsprite.filter].name);
}
if (Gargs.fussy_p) {
exit(EXIT_FAILURE);
}
if (Gsprite.filter > 1) {
/*
* default to 2x2 dither (uses no memory)
*/
Gsprite.filter = 1;
fn = filter_fn(Gsprite.filter);
assert(fn);
(*fn)(&Gpg);
}
}
if ((Gsprite.lb_bpp == 3)
&& (Ggif.output_lb_bpp < 3)) {
reduce_sprite_lb_bpp(Ggif.output_lb_bpp);
}
}
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
static process_gif_fn filter_fn(
int index) {
process_gif_fn fn = NULL;
assert(index >= 0);
assert(index < COUNT(Gfilters));
switch (Gsprite.lb_bpp) {
case 3:
fn = Gfilters[index].fn_8bpp;
break;
case 4:
fn = (Gargs.force_66_p)
? Gfilters[index].fn_16bpp_66bit
: Gfilters[index].fn_16bpp_48bit;
break;
case 5:
fn = process_gif_32bpp;
break;
default:
assert(!"internal error");
longjmp(Ggif.abortion, FAIL_INTERNAL_PROCESS_IMAGE);
}
return fn;
}
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
* this unconditionally converts an 8bpp image into either a 8bpp, 4bpp, 2bpp, or 1bpp image
* note that converting to 8bpp is useful if the source is a 16bpp image...
* note that the original *must* have a 256 entry palette attached
*/
static void reduce_sprite_lb_bpp(
int lb_bpp) {
int line_length;
int height;
byte *rove;
int original_size;
int ppw;
int bpp;
reducebitmap rb;
osspriteop_header *sprite = Gsprite.sprite;
static const os_mode modes[4] = {
os_MODE1BPP90X90, os_MODE2BPP90X90,
os_MODE4BPP90X90, os_MODE8BPP90X90};
assert(sprite);
assert(lb_bpp >= 0);
assert(lb_bpp <= 3);
Gsprite.lb_bpp = lb_bpp;
sprite->mode = modes[lb_bpp];
bpp = 1 << lb_bpp;
if (Gargs.verbose > 0) {
fprintf(Glog, " reducing image to %d bpp\n", bpp);
}
/*
* the LEGAL way to do this is to create a sprite of the required size,
* switch output to it, and then render our 256 colour sprite into it
* but fuck that, let's just move that memory about ourselves, it is
* easier
*/
line_length = 4 * WORD_WIDTH(Gsprite.pixel_size.x * bpp);
height = Gsprite.pixel_size.y;
rove = (byte *)(sprite + 1); /* start of palette */
if (sprite->image == sizeof(osspriteop_header)) {
/*
* no palette, so must be using default palette!
*/
if (Gargs.verbose > 0) {
fprintf(Glog, " the reduced image uses the default palette\n");
}
} else {
/*
* check original palette was wide enough!
*/
assert(sprite->image >= (((1 << bpp) * sizeof(os_colour_pair)) + sizeof(osspriteop_header)));
rove += (1 << bpp) * sizeof(os_colour_pair); /* end of palette */
if (Gargs.verbose > 0) {
fprintf(Glog, " the reduced image has a palette\n");
}
}
sprite->width = WORD_WIDTH(Gsprite.pixel_size.x * bpp) - 1; /* must do this! */
ppw = 32 / bpp;
sprite->right_bit = 31 - (bpp * (Gsprite.pixel_size.x % ppw)); /* and must do this! */
sprite->image = rove - ((byte *)sprite); /* offset of (new) image */
rb.source.bitmap = Gsprite.image.bitmap;
rb.source.line_length = Gsprite.image.line_length;
rb.dest.bitmap = rove;
rb.dest.line_length = line_length;
rb.bpp = bpp;
rb.pixel_size = Gsprite.pixel_size; /* structure copy */
reduce_bitmap(&rb); /* process image */
Gsprite.image.bitmap = rove; /* set up incase re-process! */
rove += line_length * height; /* end of image */
if (Gsprite.transparent_p) {
if (Gargs.verbose > 0) {
fprintf(Glog, " reducing transparency to %d bpp\n", bpp);
}
assert(sprite->image <= sprite->mask); /* we make this assumption */
assert(Gsprite.mask.line_length == Gsprite.image.line_length); /* I should hope so! */
assert(Gsprite.mask.bitmap);
sprite->mask = rove - ((byte *)sprite); /* offset of transparency */
rb.source.bitmap = Gsprite.mask.bitmap;
rb.dest.bitmap = rove;
reduce_bitmap(&rb); /* process transparency */
Gsprite.mask.bitmap = rove; /* set up incase re-process! */
Gsprite.mask.line_length = line_length; /* set up incase re-process! */
rove += line_length * height; /* end of transparency */
} else {
sprite->mask = sprite->image;
}
Gsprite.image.line_length = line_length; /* set up incase re-process! */
original_size = sprite->size;
sprite->size = rove - ((byte *)sprite);
Gsprite.area->used -= original_size - sprite->size;
}
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
* this takes a source image of resolution 8bpp
* and converts it to a destination image of resolution rb->bpp (8/4/2/1)
* it will use the lower rb->bpp of each source byte
* note can overlap, so long as if dest <= source
*/
static void reduce_bitmap(
const reducebitmap *rb) {
int x, y;
int pixel_width, word_width, bit_width;
int bpp;
int bit;
bits w, mask;
byte *source;
bits *dest;
int ppw;
int ds, dd;
assert(rb);
assert(rb->source.bitmap);
assert(rb->dest.bitmap);
assert(rb->dest.line_length > 0);
assert((rb->dest.line_length % 4) == 0); /* since we are writing words at a time */
assert((rb->bpp == 8) || (rb->bpp == 4) || (rb->bpp == 2) || (rb->bpp == 1));
bpp = rb->bpp;
bit_width = rb->pixel_size.x * bpp;
word_width = WORD_WIDTH(bit_width);
assert((word_width * 4) <= ABS(rb->dest.line_length));
assert(ABS(rb->source.line_length) >= ABS(rb->dest.line_length));
ppw = 32 / bpp;
pixel_width = word_width * ppw;
assert(pixel_width >= rb->pixel_size.x);
assert(pixel_width < (rb->pixel_size.x+ppw));
source = rb->source.bitmap;
dest = (bits *)rb->dest.bitmap;
mask = ~(~0u << bpp);
ds = rb->source.line_length - pixel_width;
dd = LSR(rb->dest.line_length, 2) - word_width;
for (y= rb->pixel_size.y; (y > 0); y--) {
/*
* go left to right because may have source == dest
*/
assert(source == (rb->source.bitmap + ((rb->pixel_size.y - y) * rb->source.line_length)));
assert(dest == (bits *)(rb->dest.bitmap + ((rb->pixel_size.y - y) * rb->dest.line_length)));
x= pixel_width;
do {
w = 0;
bit = 0;
do {
w |= (mask & *source++) << bit; /* mask to handle transparency layer */
bit += bpp;
} while (bit < 32);
*dest++ = w;
x -= ppw;
} while (x > 0);
assert(x == 0);
source += ds;
dest += dd;
}
}
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
static bool process_gif_32bpp(
const process_gif *p) {
const byte *source;
bits *dest;
int x, y, width, line_length;
const os_colour *palette;
assert(p);
assert(p->in_palette.colours);
assert(p->buffer);
palette = p->in_palette.colours;
source = p->buffer;
line_length = p->line_length;
width = p->pixel_width;
for (y= p->pixel_height; (y > 0); y--) {
dest = (bits *)source;
/*
* note HAVE to work from right to left because source == dest
*/
for (x= width - 1; (x >= 0); x--) {
dest[x] = LSR(palette[source[x]], 8);
}
source += line_length;
}
return FALSE;
}
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
* output filename in a `nice' manner into a 15 character field
*/
static void log_filename(
const char *name) {
int n;
assert(name);
n = strlen(name);
if (n < 15) {
fprintf(Glog, "%15s", name);
} else {
fprintf(Glog, "\x8c%s", name + n - 14);
}
}
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
static int stricmp(
const char *sleft,
const char *sright) {
const char *left, *right;
int i;
assert(sleft);
assert(sright);
left = sleft;
right = sright;
if (left == right) {
return 0; /* need this check for assert code */
}
while (*left) {
if (*right == '\0') {
return *left; /* left > right */
}
assert(left != sright); /* string overlap! */
assert(right != sleft); /* string overlap! */
i = tolower(*left) - tolower(*right);
if (i != 0) {
return i; /* left != right */
}
right++;
left++;
}
return -*right; /* left <= right */
}
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
* fills out a palette with entries for the default palette that the wimp assumes for the given
* log2 bits per pixel specified
*/
static void default_wimppalette(
os_colour *palette,
int lb_bpp) {
static const os_colour default_wimp_palette_4bpp[16] = {
os_COLOUR_WHITE, /* 0 */
os_COLOUR_VERY_LIGHT_GREY, /* 1 */
os_COLOUR_LIGHT_GREY, /* 2 */
os_COLOUR_MID_LIGHT_GREY, /* 3 */
os_COLOUR_MID_DARK_GREY, /* 4 */
os_COLOUR_DARK_GREY, /* 5 */
os_COLOUR_VERY_DARK_GREY, /* 6 */
os_COLOUR_BLACK, /* 7 */
os_COLOUR_DARK_BLUE, /* 8 */
os_COLOUR_LIGHT_YELLOW, /* 9 */
os_COLOUR_LIGHT_GREEN, /* a */
os_COLOUR_LIGHT_RED, /* b */
os_COLOUR_CREAM, /* c */
os_COLOUR_DARK_GREEN, /* d */
os_COLOUR_ORANGE, /* e */
os_COLOUR_LIGHT_BLUE}; /* f */
static const os_colour default_wimp_palette_2bpp[4] = {
os_COLOUR_WHITE, /* 0 */
os_COLOUR_LIGHT_GREY, /* 1 */
os_COLOUR_MID_DARK_GREY, /* 2 */
os_COLOUR_BLACK}; /* 3 */
static const os_colour default_wimp_palette_1bpp[2] = {
os_COLOUR_WHITE, /* 0 */
os_COLOUR_BLACK}; /* 1 */
static const os_colour *defaults[3] = {
default_wimp_palette_1bpp,
default_wimp_palette_2bpp,
default_wimp_palette_4bpp};
assert(palette);
switch (lb_bpp) {
case 0:
case 1:
case 2:
memcpy(palette, defaults[lb_bpp], sizeof(os_colour) << (1 << lb_bpp));
break;
case 3:
{
int red, grn, blu, tnt, idx;
for (idx= 0; (idx < 256); idx++) {
/* idx == %BGgRbrTt */
red = ((idx & 0x10) >> 1) | ((idx & 0x04) >> 0);
grn = ((idx & 0x40) >> 3) | ((idx & 0x20) >> 3);
blu = ((idx & 0x80) >> 4) | ((idx & 0x08) >> 1);
tnt = idx & 0x03;
red |= tnt; red |= red << 4; /* expand 4 bit -> 8 bit */
grn |= tnt; grn |= grn << 4;
blu |= tnt; blu |= blu << 4;
palette[idx] = (red << os_RSHIFT) | (grn << os_GSHIFT) | (blu << os_BSHIFT);
}
}
break;
default:
assert(!"internal error");
}
}
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
static void process_cli(
char **argv) {
char *cli = NULL;
char *default_params;
char *arg;
int i;
assert(argc >= 0);
assert(argv);
/*
* note we NEED to slurp in C-parsed arguments because
* a) os_get_env() will include any '>' redirection bits
* b) os_get_env() will not handle long command lines
*
* put in environment variable args first so cli can overide 'em
*
* use our own command line parsing because os_read_args() does not like
* duplicate options
*/
default_params = getenv(GIF2RPC_PARAMS);
if (default_params) {
add_arg(&cli, default_params);
}
for (i= 1; (argv[i]); i++) {
add_arg(&cli, argv[i]);
}
#define NEXTARG strtok(NULL, " \t")
#define BOOLEANARG(ARG) } else if (stricmp(arg, "-" # ARG) == 0) { Gargs . ARG ## _p = !Gargs . ARG ## _p;
#define STRINGARG(ARG) } else if (stricmp(arg, "-" # ARG) == 0) { arg = NEXTARG; if (arg) Gargs . ARG = arg; else { Gargs.help_p = TRUE; break; }
#define INTEGERARG(ARG) } else if (stricmp(arg, "-" # ARG) == 0) { arg = NEXTARG; if (arg) Gargs . ARG = (int)strtol(arg, NULL, 10); else { Gargs.help_p = TRUE; break; }
#define DEFAULTARG(ARG) } else if (!Gargs . ARG) { Gargs . ARG = arg;
for (arg= strtok(cli, " \t"); (arg); arg= NEXTARG) {
if (FALSE) {
STRINGARG(gif)
STRINGARG(sprite)
STRINGARG(filter)
STRINGARG(name)
STRINGARG(log)
BOOLEANARG(help)
BOOLEANARG(square)
BOOLEANARG(stats)
BOOLEANARG(fussy)
BOOLEANARG(accurate)
BOOLEANARG(force_66)
BOOLEANARG(quiet)
BOOLEANARG(hourglass)
BOOLEANARG(square)
BOOLEANARG(autoname)
INTEGERARG(bpp)
INTEGERARG(verbose)
} else {
if (arg[0] == '-') {
/*
* unrecognised option
*/
Gargs.help_p = TRUE;
break;
}
if (FALSE) {
DEFAULTARG(gif)
DEFAULTARG(sprite)
DEFAULTARG(filter)
DEFAULTARG(log)
DEFAULTARG(name)
} else {
/*
* not an implicit argument
*/
Gargs.help_p = TRUE;
break;
}
}
}
if ((Gargs.help_p)
|| (Gargs.gif == NULL)
|| (Gargs.sprite == NULL)) {
fprintf(stderr,
"gif2rpc: invalid command line `%s'\n"
"gif2rpc [-gif] <file> [-sprite] <file> [-filter] <name> [-log] <file> "
"[-bpp <n>] [-verbose <n>] "
"[-quiet] [-hourglass] [-accurate] [-force_66] "
"[-name <sprite>]\n"
"gif gif file to convert\n"
"sprite name for output sprite file\n"
"bpp bits-per-pixel for output sprite [-1/0/1/2/4/8/16/32]\n"
" 0 => output with gif's palette, as 1/2/4/8 bpp sprite\n"
" -1 => output as current mode with current palette\n"
" >0 => output with default (no) palette in specified bpp\n"
"quiet do not print anything if an error occurs --- see log, verbose\n"
"verbose how much detail do you want 0/1/2? --- see log, quiet\n"
"log file to which error and status information is sent --- see quiet, verbose\n"
"hourglass enable hourglass\n"
"name name of sprite in file <forced to lower case>\n"
"autoname name of sprite in file derives from the file name\n"
"square ignore aspect ratio (if output bpp >= 16)\n"
"accurate use slower code to output 32K/<= 256 colour images\n"
"force_66 force use of 66-bit accuracy when outputing 32K images\n"
"filter one of: ",
cli);
for (i= 0; (i < COUNT(Gfilters)); i++) {
fprintf(stderr, "%s%s", Gfilters[i].name, (i == (COUNT(Gfilters)-1)) ? "\n": ", ");
}
exit(EXIT_SUCCESS);
}
/*
* note can't free(cli) here because Gargs contains pointers into it for
* its' string arguments
*/
}